package bsh.meta;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.CompilationState;
import bsh.exception.CompilationAssertionFailed;

/**
 * @author takeshi
 */
public abstract class AbstractFieldDescriptor implements FieldDescriptor {

    private final TypeWrapper fieldType, declaringType;

    /**
     * @param declaringType o tipo que declara o field
     * @param fieldType o tipo do field
     */
    public AbstractFieldDescriptor(TypeWrapper declaringType, TypeWrapper fieldType) {
        super();
        this.declaringType = declaringType;
        this.fieldType = fieldType;
    }

    /**
     * {@inheritDoc}
     */
    public final boolean isFinal() {
        return (this.asmModifiers() & Opcodes.ACC_FINAL) != 0;
    }

    /**
     * {@inheritDoc}
     */
    public final void writeGetValueCode(MethodVisitor visitor, CompilationState state) {
        int opcode;
        if (!this.isStatic()) {
            state.assertMinimumStackSize(1);
            TypeWrapper popStack = state.popStack();
            if (!this.declaringType().isSuperTypeOf(popStack)) {
                throw new CompilationAssertionFailed("expected: " + this.declaringType() + " but was: " + popStack);
            }
            opcode = Opcodes.GETFIELD;
        } else {
            TypeWrapper popStack = state.popStack();
            if (!(popStack instanceof MetaTypeWrapper)) {
                throw new CompilationAssertionFailed("expected: " + this.declaringType() + " but was: " + popStack);
            }
            TypeWrapper typeLiteral = ((MetaTypeWrapper) popStack).getLiteralType();
            if (!this.declaringType().isSuperTypeOf(typeLiteral)) {
                throw new CompilationAssertionFailed("expected: " + this.declaringType() + " but was: " + popStack);
            }
            opcode = Opcodes.GETSTATIC;
        }
        visitor.visitFieldInsn(opcode, this.declaringType().internalName(), this.fieldName(), this.fieldType().typeDescription());
        state.pushStack(this.fieldType);
    }

    /**
     * {@inheritDoc}
     */
    public final void writeWriteValueCode(MethodVisitor visitor, CompilationState state) {
        int opcode;
        TypeWrapper value = state.popStack();
        if (!this.isStatic()) {
            TypeWrapper target = state.popStack();
            if (!target.equals(this.declaringType())) {
                throw new CompilationAssertionFailed("expected: " + this.declaringType() + " but was: " + target);
            }
            opcode = Opcodes.PUTFIELD;
        } else {
            TypeWrapper target = state.popStack();
            if (!(target instanceof MetaTypeWrapper)) {
                throw new CompilationAssertionFailed("expected: " + this.declaringType() + " but was: " + target);
            }
            opcode = Opcodes.PUTSTATIC;
        }
        if (!this.fieldType().isSuperTypeOf(value)) {
            throw new CompilationAssertionFailed("expected: " + this.fieldType() + " but was: " + value);
        }
        visitor.visitFieldInsn(opcode, this.declaringType().internalName(), this.fieldName(), this.fieldType().typeDescription());
    }

    /**
     * {@inheritDoc}
     */
    public final TypeWrapper fieldType() {
        return this.fieldType;
    }

    /**
     * {@inheritDoc}
     */
    public final TypeWrapper declaringType() {
        return this.declaringType;
    }

}
